﻿use std::collections::HashSet;
use std::ops::Not;
use std::sync::Arc;

use crate::core::error::operation_error::OperationError;
use crate::core::operation::Operation;
use crate::core::path_type::TypedPath;
use crate::core::step_context::StepContext;
use regex::Regex;
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tonic::async_trait;
use super::build_target::BuildTarget;

pub struct ChangeUnitySymbol {
    file_path: TypedPath,
    build_target: BuildTarget,
    adds: HashSet<String>,
    subs: HashSet<String>,
}

impl ChangeUnitySymbol {
    pub fn new<'a>(
        file_path: &TypedPath,
        build_target: BuildTarget,
        add_symbols: impl Iterator<Item = &'a str>,
        sub_symbols: impl Iterator<Item = &'a str>,
    ) -> Self {
        ChangeUnitySymbol {
            file_path: file_path.clone(),
            build_target,
            adds: add_symbols.map(|s| s.to_string()).collect(),
            subs: sub_symbols.map(|s| s.to_string()).collect(),
        }
    }
}

#[async_trait]
impl Operation for ChangeUnitySymbol {
    async fn run(&mut self, context: Arc<StepContext>) -> Result<(), OperationError> {
        if self.adds.is_empty() {
            return Ok(());
        }

        let file_path = match &self.file_path {
            TypedPath::Normal(p) => p.clone(),
            TypedPath::Contextual(s) => context.job_context().path().join(s),
        };

        let file = File::open(&file_path).await.unwrap();

        let mut lines = BufReader::new(file).lines();
        let mut handled: Vec<String> = vec![];
        let re = Regex::new(r"^(?<space>\s*)(?<platform>\d+): (?<symbols>[\w;]*)$").unwrap();

        let mut started = false;
        let mut ended = false;
        while let Some(mut l) = lines.next_line().await.unwrap() {
            if started.not() {
                if l.contains("scriptingDefineSymbols") {
                    started = true;
                }
            } else if ended.not() {
                match re.captures(&l) {
                    Some(captures) => {
                        if let Ok(platform) = captures["platform"].parse::<BuildTarget>() {
                            if self.build_target == platform {
                                let mut s = format!("{}{:?}: ", &captures["space"], platform);
                                let union: HashSet<&str> = captures["symbols"]
                                    .split(";")
                                    .chain(self.adds.iter().map(String::as_str))
                                    .collect();
                                for x in union
                                    .difference(&self.subs.iter().map(String::as_str).collect())
                                {
                                    s.push_str(x);
                                    s.push(';');
                                }
                                l = s.trim_end_matches(";").to_string();

                                ended = true;
                            }
                        }
                    }
                    None => {
                        ended = true;
                    }
                }
            }
            handled.push(l);
        }

        let mut file = File::create(&file_path).await.unwrap();

        for l in handled {
            file.write_all(l.as_ref()).await.unwrap();
            file.write("\n".as_bytes()).await.unwrap();
        }

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use crate::core::operation::change_unity_symbol::ChangeUnitySymbol;
    use crate::core::test_util::TestUtil;

    #[tokio::test]
    async fn test_run(){
        let step_context = TestUtil::get_step_context();
    }
}
